#import "GlyphManager.h"
#import "PrefsManager.h"
#import "Glyph.h"
#import <appkit/Application.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <appkit/Matrix.h>
#import <appkit/TextField.h>
#import <appkit/ScrollView.h>
#import <appkit/Panel.h>
#import <defaults/defaults.h>
#import <appkit/Font.h>
#import <sys/file.h>
#import <stdlib.h>
#import <strings.h>
#import <NXCType.h>
#import <sys/dir.h>
#include <sys/types.h>
#include <sys/stat.h>
#import "MenuManager.h"
#import "ClassManager.h"
#import "GlyphView.h"
#import "WorkspaceManager.h"
#include <sys/types.h>
#include <sys/dir.h>

extern id Nu ;
extern char bigBuf[] ; // a "big" buffer for general use



void
freeFileList(struct direct ** aList, int knt)
{ // free up a filelist created by scandir
  int i ;
  if(aList)
  { for(i = 0 ; i < knt ; i++) // free up former struct
      free(aList[i]) ; 
    free(aList) ;
    aList = NULL ;
  }
}

int isDotM(ptr)
struct direct *ptr ;
{ // returns 1 iff ptr has a .m extension.
  // We don't use rindex because nothing
  // guarantees ptr is null-terminated!
  if(ptr == NULL)
    return 0 ;
  if(ptr->d_name[ptr->d_namlen - 1] == 'm' &&
     ptr->d_name[ptr->d_namlen - 2] == '.')
    return 1 ;
  return 0 ;
}

int isDotWsd(ptr)
struct direct *ptr ;
{ // returns 1 iff ptr has a .wsd extension.
  // Currently, don't use this, but would like
  // to incomporate wsd file access into
  // the this manager.
  if(ptr == NULL)
    return 0 ;
  
  if(ptr->d_name[ptr->d_namlen - 1] == 'd' &&
     ptr->d_name[ptr->d_namlen - 2] == 's' &&
     ptr->d_name[ptr->d_namlen - 3] == 'w' &&
     ptr->d_name[ptr->d_namlen - 4] == '.')
    return 1 ;
  return 0 ;
}

int hasNoExt(ptr) 
struct direct *ptr ;
{ // returns 1 iff ptr has no extension
  if(!index(ptr->d_name,'.'))
    return 1 ;
  return 0 ;
}

int
selectFiles(ptr)
struct direct *ptr ;
{ // returns 1 iff the entry has a .m or .wsd extension, or
  // no extension at all.  else returns 0.
  if(hasNoExt(ptr) || isDotM(ptr))
    return 1 ;
  else
    return 0 ;
}


@implementation GlyphManager: Object

+ (BOOL) putPathTo: (char *) aFile startingAt: (char *) aPath  in: (char *)  thePath;
{ // Search for file (or directory aFile.  Start the search in the
  // directory aPath.  If found, return YES and put path in thePath.
  // else return NO.
  int i, fileCnt, nextPathLen ;
  struct stat statBuf ;
  struct direct **filesList ;
  BOOL rval = NO ; 
  fileCnt = scandir(aPath,&filesList,NULL,NULL) ;
  for(i = 0 ; i < fileCnt ; i++)
  { if(!strcmp(aFile, (*filesList[i]).d_name)) 
    { // if our file...
      sprintf(thePath,"%s/%s",aPath,aFile) ;
      rval = YES ;
      break ;
    }
    nextPathLen = strlen(aPath) + strlen((*filesList[i]).d_name) + 2 ;
    if(strcmp((*filesList[i]).d_name,".") && strcmp((*filesList[i]).d_name,".."))
    { // examine files, skipping "." and ".."
      char nextPath[nextPathLen] ;
      sprintf(nextPath,"%s/%s",aPath,(*filesList[i]).d_name) ;
      stat(nextPath,&statBuf) ;
      if(!(statBuf.st_mode & 0x8000)) // if a directory...
      { if(rval = [self putPathTo: aFile startingAt: nextPath in: thePath])
          break ;
      }
    }
  }
  for(i = 0 ; i < fileCnt ; i++)
    free(filesList[i]) ;
  free(filesList) ;
  return rval ;
}


- browser ;
{ return browser ;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ if(!rootPath) 
    rootPath = NXGetDefaultValue([NXApp appName],"NuPath") ;
  listedColumn = column ; 
  freeFileList(fileList,fileKnt) ; // free up former struct
  if(column == 0)
  { fileKnt = scandir(rootPath,&fileList,selectFiles,alphasort) ;
    return fileKnt ;
  }
  else
  { char aPath[1024] ;
    [sender getPath:aPath toColumn:column] ;
    sprintf(bigBuf,"%s%s",rootPath,aPath) ;
    fileKnt = scandir(bigBuf,&fileList,selectFiles,alphasort) ;
    return fileKnt ;
  }
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{ if(column != listedColumn)
    [self browser: browser fillMatrix:nil inColumn: column] ;
  if(isDotM(fileList[row]))
  { strcpy(bigBuf,(*fileList[row]).d_name) ;
    bigBuf[(*fileList[row]).d_namlen - 2] = '\0' ;
    [cell setStringValue: bigBuf] ;
    [cell setLeaf: YES] ;
  }
  else
  { [cell setLeaf: NO] ;
    [cell setStringValue: (*fileList[row]).d_name] ;
  }
  return self ;
}

- browserHit:sender ;
{ NXEvent *anEvent ;
  anEvent = [NXApp currentEvent] ;
  if(anEvent->type == NX_LMOUSEUP &&
    anEvent->data.mouse.click == 2) // if a double-click..
    [self instantiate:sender] ;
  return self ;
}

- decHeight:sender
{ [[height cell] setFloatValue: [[height cell] floatValue] - 1.0] ; 
  if([coupler state])
    [[width cell] setFloatValue:  [[height cell] floatValue]] ;
  return self;
}

- decWidth:sender
{ [[width cell] setFloatValue: [[width cell] floatValue] - 1.0] ;
  if([coupler state])
    [[height cell] setFloatValue:  [[width cell]floatValue]] ;
  return self;
}

- delete:sender ;
{ // delete the selected class or category.  a category
  // can only be deleted if it is empty, i.e. it has
  // no classes or subcategories.
  // NOTE: unload deleted classes?
  char aPath[512] ;
  int rval, selCol, selRow ;
  if((selCol = [browser selectedColumn]) == -1)
    return self ;
  selRow = [[browser matrixInColumn: selCol] selectedRow] ;
  if(![self isDotMCol:selCol row:selRow]) // remove category
  { // be sure category is empty
    { struct direct **aList ;
      [browser getPath: aPath toColumn: selCol+1] ;     
      sprintf(bigBuf,"%s%s",rootPath,aPath) ;
      rval = scandir(bigBuf,&aList,selectFiles,alphasort) ;
      freeFileList(aList,rval) ;
      if(rval > 0)
      { NXRunAlertPanel("Nu","Error: can't delete non-empty category",
           NULL,NULL,NULL) ;
        return self ;
      }
    }
    rval = NXRunAlertPanel("Nu",
     "Delete category %s?","Yes","No",NULL, aPath) ;
    if(rval == 0)
      return self ;
    sprintf(bigBuf,"rmdir %s%s > /dev/null",rootPath,aPath) ;
    system(bigBuf) ;
  }
  else // remove class
  { [browser getPath: aPath toColumn: selCol] ;     
    rval = NXRunAlertPanel("Nu",
     "Delete class %s in category %s?","Yes","No",NULL,
       [[[browser matrixInColumn: selCol] selectedCell] stringValue],aPath);
      if(rval == 0)
        return self ;
     [browser getPath: aPath toColumn: selCol+1] ;     
     sprintf(bigBuf,"rm %s%s.m 2> /dev/null",rootPath,aPath) ;
     system(bigBuf) ;
     sprintf(bigBuf,"rm %s/.h/%s.h 2> /dev/null", rootPath,
         [[[browser matrixInColumn: selCol] selectedCell] stringValue]) ;
     system(bigBuf) ;
     sprintf(bigBuf,"rm %s/.o/%s.o 2> /dev/null", rootPath,
         [[[browser matrixInColumn: selCol] selectedCell] stringValue]) ;
     system(bigBuf) ;
     sprintf(bigBuf,"rm %s/.g/%s.g 2> /dev/null", rootPath,
         [[[browser matrixInColumn: selCol] selectedCell] stringValue]) ;
     system(bigBuf) ;
  }
  [[browser window] disableFlushWindow] ;
  [browser loadColumnZero] ;
  sprintf(bigBuf,"/%s",aPath) ;
  [browser setPath: bigBuf] ;
  [[browser window] reenableFlushWindow] ;
  [[browser window] flushWindow] ;
  return self ;
}

- edit:sender
{ // This is invoked from the "inspect" button (which was
  // originally called "edit"...hence the method name).
  int selCol, selRow ;
  char aPath[512] ;
  selCol = [browser selectedColumn]  ;
  selRow = [[browser matrixInColumn: selCol] selectedRow] ;
  [browser getPath: aPath toColumn: selCol+1] ;
  sprintf(bigBuf,"%s%s.m",rootPath,aPath) ;
  if([self isDotMCol:selCol row:selRow])
    [self editFile: bigBuf 
         class: (char *) 
          [[[browser matrixInColumn: selCol] selectedCell] stringValue]] ;
  return self ;
}


- editFile: (char *) aFile class: (char *) aClass ;
{ // aClass is the name of a class, aFile is the
  // full pathname of a class definition, including
  // the .m extension.  Attempt
  // to open the definition in a class inspector window.
  // If aFile is NULL, search for aClass.  If
  // aClass is NULL, parse the class name from
  // aFile by removing the path and the extension.
  // If both aClass and aFile are NULL, do nothing!
  // Returns nil if class definition can't be found,
  // else returns self.
  id winList, aWin, manager ;
  int i,knt ;
  const char *theFlags ;
  struct stat statBuf ;
  if(!aFile && !aClass)
    return self ; // do nothing!
  if(!aClass) // no class name: get it from aFile
  { int len ;
    char *nameStart ;  
    nameStart = rindex(aFile,'/') ;
    if(!nameStart)
      return nil ;
    else
       nameStart++ ;
    len = strlen(nameStart) - 2 ; // -2 for .m at end
    aClass = alloca(len + 1) ;
    strncpy(aClass,nameStart,len) ;
    aClass[len] = '\0' ;
  }
  else if(!aFile)
  { // search for aFile
    char classExtended[128] ;
    // add extension to aClass
    sprintf(classExtended,"%s.m",aClass) ;
    // now search for the file
    aFile = alloca(2048) ;
    if(![GlyphManager putPathTo: classExtended 
          startingAt: (char *) NXGetDefaultValue([NXApp appName],"NuPath") 
          in: aFile])
      return nil ;
  }
  // do we allow multiple windows of same class?
  theFlags = NXGetDefaultValue([NXApp appName],"Flags") ;
  if(theFlags[MULTIPLECLASSWINDOWS]  == '1')
  { // check if Class is already being edited
    winList = [NXApp windowList] ;
    knt = [winList count] ;
    for(i = 0 ; i < knt ; i++)
    { aWin = [winList objectAt: i] ;
      if(!strcmp([aWin name],aClass)) // found it...
        return [aWin makeKeyAndOrderFront: self] ;
    }
  }
  // verify that the file exists
  if(stat(aFile,&statBuf))
    return nil ;
//  [Nu printf: "non nil\m"] ;
  // open class manager on this file
  manager = [NXApp loadNibSection:"ClassManager.nib" 
      owner: self] ;
  [manager init] ; // this is supposed to happen automatically
  // as part of loadNibSection:, but it don't! So I send init
  // explicitly
  [manager fileName: aFile] ;
  [manager className: aClass] ; 
  [manager readFile] ;
  [manager display] ;
  [manager makeKeyAndOrderFront: self] ;
  return self ;
}

- enterHeight:sender
{ // user has entered height ; if coupled, copy into width
  if([coupler state])
    [[width cell] setFloatValue:  [[height cell] floatValue]] ;
    return self;
}

- enterWidth:sender
{ // user has entered width ; if coupled, copy into height
  if([coupler state])
    [[height cell] setFloatValue:  [[width cell]floatValue]] ;
    return self;
}

- glyphView ;
{ return glyphView ;
}

- incHeight:sender
{ [[height cell] setFloatValue: [[height cell] floatValue] + 1.0] ;
  if([coupler state])
    [[width cell] setFloatValue:  [[height cell] floatValue]] ;
  return self;
}

- incWidth:sender
{ [[width cell] setFloatValue: [[width cell] floatValue] + 1.0] ;
  if([coupler state])
    [[height cell] setFloatValue:  [[width cell]floatValue]] ;
  return self ;
}

- instantiate:sender ;
{ // instantiate the currently selected class
  // and add it to our glyphView.  If the
  // glyph returns nil in response to width:height:,
  // then it isn't added to the glyphView.
  char className[128], classPath[256], oFile[256] ;
  int selCol, selRow ;
  id aGlyphClass ;
  Glyph * aGlyph ;
  selCol = [browser selectedColumn]  ;
  selRow = [[browser matrixInColumn: selCol] selectedRow] ;
  if([self isDotMCol:selCol row:selRow])
  { struct stat mStat,oStat ;
    strcpy(className,[[[browser matrixInColumn: selCol
                  ]selectedCell] stringValue]) ;
    // may need to compile file
    [browser getPath: classPath toColumn: selCol + 1] ;
    sprintf(bigBuf,"%s%s.m",rootPath,classPath) ;
    stat(bigBuf,&mStat) ;
    sprintf(oFile,"%s/.o/%s.o",rootPath,className) ;
    if(stat(oFile,&oStat) || (mStat.st_mtime >= oStat.st_mtime)) 
    {  [msgView setStringValue: "Compiling..."] ;
       NXPing() ;
       if(![ClassManager compileClass: className file: bigBuf])
       {  [msgView setStringValue: ""] ;
          return self ; 
       }
       [msgView setStringValue: "Loading..."] ;
       NXPing() ;
       if(![ClassManager load: className])
       { [msgView setStringValue: ""] ;
         return self ;
       }
    }
    else if([ClassManager loadIndex:className] == -1)
    { [msgView setStringValue: "Loading..."] ;
       NXPing() ;
       if(![ClassManager load: className])
       { [msgView setStringValue: ""] ;
         return self ;
       }
    }
    [msgView setStringValue: "Instantiating..."] ;
    NXPing() ;
    aGlyphClass = objc_getClass(className) ;
    aGlyph = [[aGlyphClass alloc]
        init: [[width cell] floatValue]
      :[[height cell] floatValue]] ;
    if(aGlyph)
    { NXRect aRect ; 
      aRect.origin.x = aRect.origin.y = 0.0 ;
      [[glyphView superview] getDocVisibleRect: &aRect] ;
      [aGlyph moveTo: aRect.origin.x :aRect.origin.y]  ;
      [[glyphView rootGlyph] plant: aGlyph] ;
      [aGlyph getFrame: &aRect] ;
      [[glyphView rootGlyph] display: &aRect from: nil] ;
    }
  }
  [msgView setStringValue: ""] ;
  return self ;
}

- (BOOL) isDotMCol: (int) col row:(int) row ;
{ // returns YES iff file in browser at col,row is
  // a dot M file.  The decision is made by
  // examining the entries first char: if it
  // is upper case, then its assumed to be a .m file
  const char *fName ;
  fName = [[[browser matrixInColumn: col] cellAt: row :0] stringValue] ;
  if(fName)
    return NXIsUpper((unsigned) fName[0]) ;
  else
    return NO ;
}

- nameTextCell ;
{ // return id of cell in widget labelled Name:
  return [name cell] ;
}

- newCat:sender 
{ char aPath[512], newName[128] ;
  int selCol, selRow ;
  strcpy(newName,[[name cell] stringValue]) ;
  if(newName[0] == '\0') // nothing there !
    return self ; 
  if(NXIsUpper((unsigned) newName[0]))
  { NXRunAlertPanel("Nu","Error: categories must not begin capitalized",
     NULL,NULL,NULL) ;
    return self ;
  }
  if((selCol = [browser selectedColumn]) != -1)
  { selRow = [[browser matrixInColumn: selCol] selectedRow] ;
    if(fileList[0]) // if not empty column
      if([self isDotMCol:selCol row:selRow]) // can't add category
        return self ; // to a leaf
    [browser getPath: aPath toColumn: selCol + 1 ] ;     
  }
  else
    aPath[0] = '\0' ;
  sprintf(bigBuf,"cd %s%s ; mkdir %s > /dev/null",rootPath,aPath,newName) ;
  system(bigBuf) ;
  [[browser window] disableFlushWindow] ;
  [browser loadColumnZero] ;
  sprintf(bigBuf,"/%s/%s",aPath,newName) ;
  [browser setPath: bigBuf] ;
  [[browser window] reenableFlushWindow] ;
  [[browser window] flushWindow] ;
  return self ;
  return self ;
}
  
- newClass:sender
{ char aPath[512], fileBuf[512], className[128] ;
  int selCol, selRow ;
  strcpy(className,[[name cell] stringValue]) ;
  if(className[0] == '\0') // nothing there !
    return self ; 
  if(!NXIsUpper((unsigned) className[0]))
  { NXRunAlertPanel("Nu","Error: class names must begin capitalized",
     NULL,NULL,NULL) ;
    return self ;
  }
  selCol = [browser selectedColumn] ;
  selRow = [[browser matrixInColumn: selCol] selectedRow] ;
  if([self isDotMCol:selCol row:selRow]) // can't add class
  { NXRunAlertPanel("Nu","Error: select a category, not a class",
       NULL,NULL,NULL) ;
    return self ; // to a leaf
  }
  else
    [browser getPath: aPath toColumn: selCol + 1 ] ;     
  // create the new class's initial text
  sprintf(fileBuf,
    "#import \\\"%s.h\\\"\n"
    "#import \\\"Nutation.h\\\"\n"
    "  // Superclass file goes on next line:\n"
    "#pragma .h #import \\\"Glyph.h\\\"\n"
    "@implementation %s:Glyph\n"
    "{\n"
    "}\n"
    "\n"
    "@end\n",
  className, className) ;  
  // write it to a file
  sprintf(bigBuf,"cd %s%s ; echo \"%s\" > %s.m",
       rootPath,aPath,fileBuf,className) ; 
  system(bigBuf) ; 
  [[browser window] disableFlushWindow] ;
  [browser loadColumnZero] ;
  sprintf(bigBuf,"/%s/%s",aPath,className) ;
  [browser setPath: bigBuf] ;
  [[browser window] reenableFlushWindow] ;
  [[browser window] flushWindow] ;
  return self ;
}


- rename:sender 
{ // NOTE: must rename .o,.h, and .g, if they
  // exist!!!
  char aPath[512], newName[128] ;
  int selCol, selRow ;
  strcpy(newName,[[name cell] stringValue]) ;
  if(newName[0] == '\0') // nothing there !
    return self ; 
  if(NXIsUpper((unsigned) newName[0]))
  { NXRunAlertPanel("Nu","Error: categories must not begin capitalized",
     NULL,NULL,NULL) ;
    return self ;
  }
  if((selCol = [browser selectedColumn]) != -1)
  { selRow = [[browser matrixInColumn: selCol] selectedRow] ;
    if(fileList[0]) // if not empty column
    { if([self isDotMCol:selCol row:selRow]) // can't rename .m 
      { NXRunAlertPanel("Nu","Error: cannot rename a class",
           NULL,NULL,NULL) ;
        return self ; 
      }
    }
    [browser getPath: aPath toColumn: selCol] ;     
  }
  else
    aPath[0] = '\0' ;
  sprintf(bigBuf,"cd %s%s ; mv %s  %s> /dev/null",rootPath,aPath,
     [[[browser matrixInColumn: selCol] selectedCell] stringValue],
     newName) ;
  system(bigBuf) ;
  [[browser window] disableFlushWindow] ;
  [browser loadColumnZero] ;
  sprintf(bigBuf,"/%s/%s",aPath,newName) ;
  [browser setPath: bigBuf] ;
  [[browser window] reenableFlushWindow] ;
  [[browser window] flushWindow] ;
  return self ;
}
  

- setBrowser: anObject ;
{ browser = anObject ;
  [browser setDelegate: self] ;
  return self ;
}


- setMsgView: anObject ;
{ msgView = anObject ;
  return self ;
}

- setup ;
{ // initialize myself
  Window *aWin ;
  aWin = [browser window] ;
  [aWin disableFlushWindow] ;
  [browser loadColumnZero] ;
  [browser setPath: "/glyphs"] ;
  [aWin reenableFlushWindow] ;
  [aWin flushWindow] ;
  [glyphView setGlyphMsgView: msgView] ; // set to my msg view
  // this next message must come AFTER setGlyphMsgView:, as
  // setGlyphMsgView: calls GlyphView's setUpNew, which sets the
  // freeWhenClosed flag to YES.  But we don't ever want to free
  // the glyphView in the Glyph Browser:
  [glyphView setFreeWhenClosed: NO] ;  // always keep glyphView around
  return self ;
}



@end
